package com.tj.collection.interceptors;

import com.alibaba.fastjson.JSON;
import com.fqgj.base.services.redis.RedisClient;
import com.fqgj.common.utils.MD5;
import com.fqgj.common.utils.StringUtils;
import com.fqgj.exception.common.ApplicationException;
import com.fqgj.log.factory.LogFactory;
import com.fqgj.log.interfaces.Log;
import com.qianli.cashmama.product.facade.result.ProductCategory;
import com.tj.collection.aop.anno.CheckProductCategory;
import com.tj.collection.aop.anno.ClickOnce;
import com.tj.collection.aop.anno.VisitorAccessible;
import com.tj.collection.bean.RedisKey;
import com.tj.collection.bean.RequestBasicInfo;
import com.tj.collection.bean.SystemConstants;
import com.tj.collection.common.RequestLocalInfo;
import com.tj.collection.db.domain.Admin;
import com.tj.collection.db.enums.RestErrorCodeEnum;
import com.tj.collection.db.enums.RoleKeyEnum;
import com.tj.collection.db.service.AdminRoleService;
import com.tj.collection.db.service.AdminService;
import com.tj.collection.filters.CrosFilter;
import com.tj.collection.integration.ProductIntegrationService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.List;

/**
 * Created with IntelliJ IDEA.
 * User: sm
 * Date: 16/9/5
 * Time: 下午2:27
 */
@Component
public class UserAccessRightInterceptor implements HandlerInterceptor {

    private static final Log log = LogFactory.getLog(UserAccessRightInterceptor.class);

    @Autowired
    private AdminService adminService;
    @Autowired
    private ProductIntegrationService productIntegrationService;
    @Autowired
    private AdminRoleService adminRoleService;
    @Autowired
    private RedisClient redisClient;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) throws Exception {
        response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));
        response.setHeader("Access-Control-Allow-Credentials", "true");
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "Content-Type,Accept,X-Requested-With,remember-me,bid,accessToken,productCategory,appCode,basicParams");

        String requestURI = request.getRequestURI();
        for (String s : CrosFilter.ALLOWED_PATHS) {
            if (requestURI.toLowerCase().contains(s)) {
                log.info("拦截器放行,url:{}", requestURI);
                return true;
            }
        }

        String accessToken = request.getHeader("accessToken");
        String productCategory = request.getHeader("productCategory");
        String basicParams = request.getHeader("basicParams");
        String domainHost = request.getHeader("Host");

        RequestLocalInfo.reset();

        //设备信息
        this.setRequestBasicInfo(basicParams, productCategory, domainHost);

        if (StringUtils.isNotEmpty(basicParams)) {
            RequestBasicInfo basicVO = JSON.parseObject(basicParams, RequestBasicInfo.class);
            if (StringUtils.isNotEmpty(basicVO.getAppClient()) && !basicVO.getAppClient().contains("web")) {
                log.info("==安卓请求不可用==");
                throw new ApplicationException(RestErrorCodeEnum.TOKEN_NOT_AVAILABLE, "Invalid token, please login again.");
            }
        }

        if (StringUtils.isNotEmpty(accessToken)) {
            HandlerMethod handlerMethod = (HandlerMethod) o;
            CheckProductCategory checkProductCategory = handlerMethod.getMethodAnnotation(CheckProductCategory.class);
            if (checkProductCategory != null) {
                if (StringUtils.isEmpty(productCategory) || "null".equalsIgnoreCase(productCategory)) {
                    throw new ApplicationException(RestErrorCodeEnum.SYSTEM_ERROR);
                }
            }

            if (!hasPermission(accessToken, productCategory)) {
                throw new ApplicationException(RestErrorCodeEnum.TOKEN_NOT_AVAILABLE, "Invalid token, please login again.");
            }
            if (!concurrencyCheck(request, o)) {
                throw new ApplicationException(RestErrorCodeEnum.CLICK_ONLY_ONCE);
            }

            //记录操作日志
            recordOperateLog(request, accessToken);
        } else {
            if (o instanceof HandlerMethod) {
                HandlerMethod handlerMethod = (HandlerMethod) o;
                CheckProductCategory checkProductCategory = handlerMethod.getMethodAnnotation(CheckProductCategory.class);
                if (checkProductCategory != null) {
                    if (StringUtils.isEmpty(productCategory) || "null".equalsIgnoreCase(productCategory)) {
                        throw new ApplicationException(RestErrorCodeEnum.SYSTEM_ERROR);
                    }
                }

                VisitorAccessible annotation = handlerMethod.getMethodAnnotation(VisitorAccessible.class);
                if (annotation != null) {
                    return true;
                }

                if (!request.getMethod().equals("OPTIONS")) {
                    if (!handlerMethod.getMethod().getName().toUpperCase().equals("LOGIN") && !handlerMethod.getMethod().getName().toUpperCase().equals("SENDVERIFYCODE")) {
                        throw new ApplicationException(RestErrorCodeEnum.TOKEN_NOT_AVAILABLE, "need login to access");
                    }
                }
            } else {
                log.info("HandlerMethod not,o:{},url:{}", o.getClass().getName(), request.getRequestURI());
            }
        }


        return true;
    }

    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
        concurrencyRemove(httpServletRequest, o);
    }

    /**
     * 用户token校验
     *
     * @param accessToken
     * @param productCategory
     * @return
     */
    private Boolean hasPermission(String accessToken, String productCategory) {
        Admin admin = adminService.getByAccessToken(accessToken);
        if (admin == null) {
            return false;
        }
        //设置当前的产品类目
        admin.setProductCategory(productCategory);
        //cashmama的MULTIPLE_MERCHANT角色 可以看到所有的产品类目 -同时对应商户
        Long fundManagerRoleId = adminRoleService.getRoleIdByRoleKey(RoleKeyEnum.MULTIPLE_MERCHANT, SystemConstants.CASHMAMA);
        List<Long> roleIdsByAdminId = adminRoleService.getRoleIdsByAdminId(admin.getId());
        if (roleIdsByAdminId.contains(fundManagerRoleId)) {
            ProductCategory byProductCategory = productIntegrationService.getByProductCategory(productCategory);
            if (byProductCategory != null) {
                admin.setMerchantCode(byProductCategory.getMerchantCode());
            }
        }
        log.info("hasPermission日志,admin:{},fundManagerRoleId:{}", JSON.toJSONString(admin), fundManagerRoleId);
        RequestLocalInfo.putCurrentAdmin(admin);
        return true;
    }

    /**
     * 设置基本参数
     *
     * @param basicParams
     * @param productCategory
     * @param domainHost
     */
    private void setRequestBasicInfo(String basicParams, String productCategory, String domainHost) {
        if (StringUtils.isEmpty(basicParams)) {
            RequestBasicInfo requestBasicInfo = new RequestBasicInfo();
            requestBasicInfo.setProductCategory(productCategory);
            requestBasicInfo.setHost(domainHost);
            RequestLocalInfo.putCurrentRequestBasicVO(requestBasicInfo);
            return;
        }
        RequestBasicInfo basicVO = JSON.parseObject(basicParams, RequestBasicInfo.class);
        basicVO.setProductCategory(productCategory);
        basicVO.setHost(domainHost);
        RequestLocalInfo.putCurrentRequestBasicVO(basicVO);
    }

    private void recordOperateLog(HttpServletRequest request, String accessToken) {
        Admin tokenAdmin = adminService.getByAccessToken(accessToken);
        Admin admin = adminService.getOneAdminByAdmin(tokenAdmin.getId());
        String scheme = request.getScheme();
        String ipAdd = "";
        try {
            ipAdd = this.getIpAddr(request);
        } catch (Exception e) {
            log.info("get request ip address exception");
        }
        int serverPort = request.getServerPort();
        String requestURL = request.getRequestURI();
        log.info("Record administrator operation log, adminAccount=" + admin.getAccount() + ";adminName=" + admin.getName() + ";scheme=" + scheme + ";IP=" + ipAdd + ";serverPort=" + serverPort + ";requestUrl=" + requestURL);
    }

    public String getIpAddr(HttpServletRequest request) throws Exception {
        String ip = request.getHeader("X-Real-IP");
        if (!org.apache.commons.lang.StringUtils.isBlank(ip) && !"unknown".equalsIgnoreCase(ip)) {
            return ip;
        }
        ip = request.getHeader("X-Forwarded-For");
        if (!org.apache.commons.lang.StringUtils.isBlank(ip) && !"unknown".equalsIgnoreCase(ip)) {
            // 多次反向代理后会有多个IP值，第一个为真实IP。
            int index = ip.indexOf(',');
            if (index != -1) {
                return ip.substring(0, index);
            } else {
                return ip;
            }
        } else {
            return request.getRemoteAddr();
        }
    }

    /**
     * 并发检测
     *
     * @param request
     */
    private boolean concurrencyCheck(HttpServletRequest request, Object o) {
        if (request.getMethod().equals("OPTIONS")) {
            return true;
        }
        if (o instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) o;
            ClickOnce annotation = handlerMethod.getMethodAnnotation(ClickOnce.class);
            if (annotation == null) {
                return true;
            }
            if (RequestLocalInfo.getCurrentAdmin() == null) {
                return true;
            }
            StringBuilder requestBody = new StringBuilder();
            try {
                BufferedReader bufferedReader = request.getReader();
                String line = null;
                while ((line = bufferedReader.readLine()) != null) {
                    requestBody.append(line);
                }
            } catch (IOException e) {
            }
            String requestBodyMd5 = MD5.md5(requestBody.length() == 0 ? "" : requestBody.toString());
            String requestURI = request.getRequestURI();
            String adminId = RequestLocalInfo.getCurrentAdmin().getId() + "";
            String key = RedisClient.format(RedisKey.INTERFACE_CONCURRENCY_KEY, requestURI, adminId, requestBodyMd5);
            if (redisClient.get(key) != null) {
                log.info("接口防并发,key:{}", key);
                return false;
            }
            redisClient.set(key, adminId, 5);
            return true;
        }
        return true;
    }

    /**
     * key 移除
     *
     * @param request
     */
    private void concurrencyRemove(HttpServletRequest request, Object o) {
        if (request.getMethod().equals("OPTIONS")) {
            return;
        }
        if (o instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) o;
            ClickOnce annotation = handlerMethod.getMethodAnnotation(ClickOnce.class);
            if (annotation == null) {
                return;
            }
            if (RequestLocalInfo.getCurrentAdmin() == null) {
                return;
            }
            StringBuilder requestBody = new StringBuilder();
            try {
                BufferedReader bufferedReader = request.getReader();
                String line = null;
                while ((line = bufferedReader.readLine()) != null) {
                    requestBody.append(line);
                }
            } catch (IOException e) {
            }
            String requestBodyMd5 = MD5.md5(requestBody.length() == 0 ? "" : requestBody.toString());
            String requestURI = request.getRequestURI();
            String adminId = RequestLocalInfo.getCurrentAdmin().getId() + "";
            String key = RedisClient.format(RedisKey.INTERFACE_CONCURRENCY_KEY, requestURI, adminId, requestBodyMd5);
            if (redisClient.get(key) != null) {
                redisClient.del(key);
            }
        }
        return;
    }
}
